package aqs;

import java.util.concurrent.locks.ReentrantLock;

/**
 * 1、lock这把锁具有高扩展性，它是通过实现lock及内部类继承aqs，所有的逻辑都是由你把控
 * 2、直观看到了写一把锁，只需要1分钟，就能够出来一个锁
 * */
public class LockAndAqs {

    static CreateLock lock = new CreateLock();

    static ReentrantLock lock1 = new ReentrantLock();

    public static void main(String[] args) throws Exception{
        /**
         * 我们一共可以复写5个方法
         * tryAcquire、tryRelease。排他锁的实现，这两个方法是我们需要自定义，aqs的模板方法会调用这两个方法进行排他锁的实现
         * tryAcquireShared、tryReleaseShared。共享锁的
         * isHeldExclusively 看线程执行拥有情况
         *
         * 你 -- Lock.lock -- sync.acquire(模板方法) -- lock里边你复写的那五个方法之一（最终由你决定）
         */
        /**
         * 公平锁：现在有abc三条线程，a获取到了锁，bc线程获取失败，bc线程会加入到同步队列的尾端。
         * a线程释放锁后，b线程收到唤醒通知，进行锁的争抢。***就在这时，新线程d也来进行争抢，
         * d线程看到b正在排队（调用hasQueuedPredecessors进行判断），就加入同步队列的尾端。
         *
         * 非公平锁：现在有abc三条线程，a获取到了锁，bc线程获取失败，bc线程会加入到同步队列的尾端。
         *          a线程释放锁后，b线程收到唤醒通知，进行锁的争抢。*** 就在这时，一个新来的线程d也来争抢，它会直接和b线程进行争抢，
         *          如果争抢失败，那么加入同步队列的尾端，对线程b和c不公平。
         * 公平锁和非公平锁都是使用的一个同步队列的实现，公平和非公平是发生在争抢时刻
         * AQS方法：
         * compareAndSetState：如果cas获取锁成功，就把线程设置为当前正在执行的线程
         * acquire：第一个模板方法
         *          tryAcquire
         *              当对排他锁加锁的过程中，看它的状态是不是等于0，如果等于0说明没有其他线程竞争这把锁，我们可以肆无忌惮进行竞争，方式就是通过cas把0替换成acquires参数，然后把当前线程设置为执行线程，然后return true；
         *              如果当前线程是我还是我加的锁就要执行可重入了，然后设置state；对于其他情况都是加锁失败，return false。【nextc < 0 int溢出 2147483647】
         *              getState：我们的lock不推荐使用在for循环里面，因为每次循环都会有state+1的操作，推荐使用在外边
         *          addWaiter
         *              它主要做将我们获取锁失败的线程添加到AQS里边的FIFO的双端队列里边，我们称之为同步队列。
         *              如果想把当前node加入同步队列的尾部，需要创建一个node实例，拿到同步队列的尾节点，如果尾节点不为空的话，将新node节点的前置节点置为尾节点，然后通过cas将node添加到尾部，cas成功，把前置节点的next节点指向当前node节点，返回node。
         *              enq():
         *                  将node t设置为尾节点，如果t为空，说明同步队列没有尾节点，说明同步队列需要进行初始化，需要将一个新的node设置为头节点，让我们的tail和head指向当前空node；
         *                  如果t不为null，说明同步队列是存在的，node节点的前置节点设为t，然后cas交换，最后将我们tail的next指向node，返回t
         *          acquireQueued
         *              定义了failed，如果为true，取消正在进行的获取尝试；定义了interrupted，
         *              死循环里，拿到当前节点的前置节点，如果当前节点的前置节点是head节点，并且加锁成功了，将当前节点设置为head节点，当前head节点的next节点指向null，help gc，failed =false，return interrupted,没有被打断
         *              shouldParkAfterFailedAcquire：获取锁失败的时候要不要进行一个park
         *                  拿到前置节点的等待状态，如果前置节点等于-1就是一个等待状态，他就会进行一个安全的park，直接return true
         *                  如果状态>0，前置节点是一个cancel状态，用do while循环进行cancel状态节点的移除
         *                  其他状态用cas将状态设置为SIGNAL状态
         *                  if (shouldParkAfterFailedAcquire(p, node) &&
         *                     parkAndCheckInterrupt()) 为true，将interrupted设为true，清除撞断标志
         *
         *          selfInterrupt
         *              增加一个中断标记，前面已经清除了还没有获取到锁，那还得再打回来
         */
        /**
         * AQS方法
         */
        lock1.lock();
        /**
         * AQS方法：尝试加锁，成功则成功，失败则失败，不会进行等待和排队
         * Lock锁的可重入是通过代码控制的：current == getExclusiveOwnerThread()，判断当前线程是否为执行线程
         * 1.lock这个方法：必须要进行加锁，不成功就一直等待，涉及到我们的同步队列
         * 2.tryLock这个方法：只尝试一次
         * 什么情况下使用tryLock。如果你的线程不希望被阻塞，你可以将tryLock与while循环进行结合。
         * 如果tryLock失败，那么就借助我们的while循环再次进行尝试加锁。（其实就是用while+tryLock进行的CAS操作）
         */
        lock1.tryLock();
        /**
         * AQS方法：可以超时获取锁
         * LockSupport.parkNanos(this, nanosTimeout);没有到时间再等会
         */
        lock1.tryLock(0,null);
        /**
         * AQS方法：支持可中断获取锁
         *
         */
        lock1.lockInterruptibly();
        /**
         * AQS方法：第二个重点复写的方法（非公平锁）：tryRelease
         */
        lock1.unlock();

        createLock();
    }

    public static void createLock() {
        Thread a = new Thread(()->{
            testLock();
            while (true){
            }
        });
        a.setName("like-lisensen-your-head");
        a.start();

        Thread b = new Thread(()->{
            testLock();
        });
        b.setName("unlike-lisensen-your-head");
        b.start();
    }
    public static void  testLock(){
        lock.lock();
        try{
            System.out.println(Thread.currentThread().getName()+"coming");
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
